﻿\subsection{Вычисление машинного эпсилона}

Машинный эпсилон --- это самая маленькая гранула, с которой может работать \ac{FPU} 
\footnote{В русскоязычной литературе встречается также термин \q{машинный ноль}.}.
Чем больше бит выделено для числа с плавающей точкой, тем меньше машинный эпсилон.
Это $2^{-23} = 1.19e-07$ для \Tfloat и $2^{-52} = 2.22e-16$ для \Tdouble.
См.также: \href{http://link.yurichev.com/17368}{статью в Wikipedia}.
% TODO recheck values

Любопытно, что вычислить машинный эпсилон очень легко:

\lstinputlisting[style=customc]{patterns/17_unions/epsilon/float.c}

Что мы здесь делаем это обходимся с мантиссой числа в формате IEEE 754 как с целочисленным числом и прибавляем
единицу к нему.
Итоговое число с плавающей точкой будет равно $starting\_value+machine\_epsilon$, так что нам
нужно просто вычесть изначальное значение (используя арифметику с плавающей точкой) чтобы измерить, 
какое число отражает один бит в одинарной точности (\Tfloat).
\IT{union} здесь нужен чтобы мы могли обращаться к числу в формате IEEE 754 как к обычному целочисленному.
Прибавление 1 к нему на самом деле прибавляет 1 к \IT{мантиссе} числа, хотя, нужно сказать,
переполнение также возможно, что приведет к прибавлению единицы к экспоненте.

\subsubsection{x86}

\lstinputlisting[caption=\Optimizing MSVC 2010,style=customasmx86]{patterns/17_unions/epsilon/float_MSVC_2010_Ox_RU.asm}

Вторая инструкция \INS{FST} избыточная: нет необходимости сохранять входное значение в этом же месте
(компилятор решил выделить переменную $v$ в том же месте локального стека, где находится и 
входной аргумент).
Далее оно инкрементируется при помощи \INS{INC}, как если это обычная целочисленная переменная.
Затем оно загружается в FPU как если это 32-битное число в формате IEEE 754, \INS{FSUBR} делает остальную
часть работы и результат в \TT{ST0}.
Последняя пара инструкций \INS{FSTP}/\INS{FLD} избыточна, но компилятор не соптимизировал её.

\subsubsection{ARM64}

Расширим этот пример до 64-бит:

\lstinputlisting[label=machine_epsilon_double_c,style=customc]{patterns/17_unions/epsilon/double.c}

В ARM64 нет инструкции для добавления числа к D-регистру в FPU, так что входное значение
(пришедшее в D0) в начале копируется в \ac{GPR},
инкрементируется, копируется в регистр FPU \TT{D1}, затем происходит вычитание.

\lstinputlisting[caption=\Optimizing GCC 4.9 ARM64,style=customasmARM]{patterns/17_unions/epsilon/double_GCC49_ARM64_O3_RU.s}

Смотрите также этот пример скомпилированный под x64 с SIMD-инструкциями: \myref{machine_epsilon_x64_and_SIMD}.

\subsubsection{MIPS}

\myindex{MIPS!\Instructions!MTC1}
Новая для нас здесь инструкция это \INS{MTC1} (\q{Move To Coprocessor 1}), она просто переносит данные из \ac{GPR} в регистры FPU.

\lstinputlisting[caption=\Optimizing GCC 4.4.5 (IDA),style=customasmMIPS]{patterns/17_unions/epsilon/MIPS_O3_IDA.lst}

\subsubsection{\Conclusion}

Трудно сказать, понадобится ли кому-то такая эквилибристика в реальном коде,
но как уже было упомянуто много раз в этой книге, этот пример хорошо подходит для объяснения формата
IEEE 754 и \IT{union} в \CCpp.

